from random import choice
from random import randrange as random
from typing import List, Tuple

try:
    from .entities import Metal, Mountain, River, Stone, Tree, Wood
    from .grid import Block
    from .utils import ceil as ceilstep
except:
    from entities import (Metal, Mountain, River, Stone, Tree,  # type: ignore
                          Wood)
    from grid import Block  # type: ignore
    from utils import ceil as ceilstep  # type: ignore

__all__ = ['random_board']


def random_board(
    to_create: Tuple[Tuple[type, float]],
    width: int = 20,
    height: int = 20,
    row_first: bool = True
) -> List[List[Block]]:
    if not row_first:
        board = [[Block() for x in range(width)] for y in range(height)]
    else:
        board = [[Block() for x in range(height)] for y in range(width)]
    for c in to_create:
        if c[0] == River:
            _random_river(board, c[1])
        elif c[0] == Mountain:
            _random_mountain(board, c[1])
        elif c[0] == Tree:
            _random_tree(board, c[1])
        elif c[0] == Wood:
            _random_wood(board, c[1])
        elif c[0] == Stone:
            '_random_stone(board, c[1])'
        elif c[0] == Metal:
            '_random_metal(board, c[1])'
    return board


def _random_river(
    board: List[List[Block]],
    ratio: float,
    rivercls: type = River
) -> List[List[Block]]:
    """Creates a random river on the board.

    Args:
        - `board` (`List[List[Block]]`): The board.
        - `ratio` (`float`): The ratio of area of river to total area.
        - `rivercls` (`type`, optional): The river class to use. Defaults
        to `River`.

    Returns:
        - `List[List[Block]]`: Edits the board directly and returns it.
    """
    WIDTH: int = len(board)
    if not WIDTH:
        return board
    HEIGHT: int = len(board[0])
    AREA: int = WIDTH * HEIGHT
    riverarea: int
    thick: int
    if random(0, 2, 1):
        go_width = True
        riverarea = ceilstep(int(AREA * ratio), step=WIDTH)
        thick = riverarea // WIDTH
    else:
        go_width = False
        riverarea = ceilstep(int(AREA * ratio), step=HEIGHT)
        thick = riverarea // HEIGHT
    if not riverarea:
        return board
    if go_width:
        isblocked: List[bool] = [not board[0][r].place_river()
                                 for r in range(HEIGHT)]
        choices: List[int] = []
        for r in range(HEIGHT - thick):
            if not any([isblocked[r2] for r2 in range(r, r + thick + 1)]):
                choices.append(r)
        r = choice(choices)
        for c in range(0, WIDTH):
            isblocked: List[bool] = [not board[c][r2].place_river()
                                     for r2 in range(HEIGHT)]
            choices: List[int] = []
            if r > 0 and not any([isblocked[i] for i in range(r - 1, r + thick)]):
                choices.append(r - 1)
            if r >= 0 and not any([isblocked[i] for i in range(r, r + thick + 1)]):
                choices.append(r)
            if r + thick + 1 < HEIGHT and not any([isblocked[i] for i in range(r + 1, r + thick + 2)]):
                choices.append(r + 1)
            r = choice(choices)
            for r2 in range(r, r + thick + 1):
                board[c][r2].append(rivercls())
    else:
        isblocked: List[bool] = [not board[c][0].place_river()
                                 for c in range(WIDTH)]
        choices: List[int] = []
        for c in range(WIDTH - thick):
            if not any([isblocked[c2] for c2 in range(c, c + thick + 1)]):
                choices.append(c)
        c = choice(choices)
        for r in range(0, HEIGHT):
            isblocked: List[bool] = [not board[c2][r].place_river()
                                     for c2 in range(WIDTH)]
            choices: List[int] = []
            if c > 0 and not any([isblocked[i] for i in range(c - 1, c + thick)]):
                choices.append(c - 1)
            if c >= 0 and not any([isblocked[i] for i in range(c, c + thick + 1)]):
                choices.append(c)
            if c + thick + 1 < WIDTH and not any([isblocked[i] for i in range(c + 1, c + thick + 2)]):
                choices.append(c + 1)
            c = choice(choices)
            for c2 in range(c, c + thick + 1):
                board[c2][r].append(rivercls())
    return board


def _random_mountain(
    board: List[List[Block]],
    ratio: float,
    mountaincls: type = Mountain,
    mountainsize: int = 15
) -> List[List[Block]]:
    """Creates random mountains on the board.

    Args:
        - `board` (`List[List[Block]]`): The board.
        - `ratio` (`float`): The ratio of area of mountains to total area.
        - `mountaincls` (`type`, optional): The mountain class to use.
        Defaults to `Mountain`.
        - `mountainsize` (`int`, optional): The maximum size of a mountain.

    Returns:
        - `List[List[Block]]`: Edits the board directly and returns it.
    """
    WIDTH: int = len(board)
    if not WIDTH:
        return board
    HEIGHT: int = len(board[0])
    AREA: int = WIDTH * HEIGHT
    mountainarea: int = int(AREA * ratio)
    if not mountainarea:
        return board
    mountains = mountainarea // mountainsize
    for i in range(mountains):
        placeable = []
        for c in range(WIDTH):
            for r in range(HEIGHT):
                if board[c][r].place_mountain():
                    placeable.append((c, r))
        now = choice(placeable)
        thissize = mountainarea // (mountains - i)
        # print(thissize)
        for _ in range(thissize):
            board[now[0]][now[1]].append(mountaincls())
            mountainarea -= 1
            placeable = []
            for cp, rp in (
                (-1, 0),
                (0, -1),
                (0, 1),
                (1, 0),
            ):
                if 0 <= now[0] + cp < WIDTH and 0 <= now[1] + rp < HEIGHT and board[now[0] + cp][now[1] + rp].place_mountain():
                    placeable.append((now[0] + cp, now[1] + rp))
            if not placeable:
                break
            now = choice(placeable)
    return board


def _random_tree(
    board: List[List[Block]],
    ratio: float,
    treecls: type = Tree
) -> List[List[Block]]:
    """Creates random trees on the board.

    Args:
        - `board` (`List[List[Block]]`): The board.
        - `ratio` (`float`): The ratio of area of trees to total area.
        - `treecls` (`type`, optional): The tree class to use. Defaults
        to `Tree`.

    Returns:
        - `List[List[Block]]`: Edits the board directly and returns it.
    """
    WIDTH: int = len(board)
    if not WIDTH:
        return board
    HEIGHT: int = len(board[0])
    AREA: int = WIDTH * HEIGHT
    treearea: int = int(AREA * ratio)
    for i in range(treearea):
        choices = []
        for c in range(WIDTH):
            for r in range(HEIGHT):
                if board[c][r].place_tree():
                    choices.append(board[c][r])
        choice(choices).append(treecls())
    return board


def _random_wood(
    board: List[List[Block]],
    ratio: float,
    woodcls: type = Wood
) -> List[List[Block]]:
    """Creates random wood minerals on the board.

    Args:
        - `board` (`List[List[Block]]`): The board.
        - `ratio` (`float`): The ratio of area of wood to total area.
        - `woodcls` (`type`, optional): The wood class to use. Defaults
        to `Wood`.

    Returns:
        - `List[List[Block]]`: Edits the board directly and returns it.
    """
    WIDTH: int = len(board)
    if not WIDTH:
        return board
    HEIGHT: int = len(board[0])
    AREA: int = WIDTH * HEIGHT
    woodarea: int = int(AREA * ratio)
    choices = []
    for c in range(WIDTH):
        for r in range(HEIGHT):
            for e in board[c][r]:
                if isinstance(e, Tree):
                    choices.append(board[c][r])
                    break
    if len(choices) < woodarea:
        for c in choices:
            c.append(woodcls())
        return board
    for _ in range(woodarea):
        c = choice(choices)
        choices.remove(c)
        c.append(woodcls())
    return board


if __name__ == '__main__':
    #print(random_board(6, 5))
    print(*random_board(6, 5), sep='\n')
